// 分配内核存储空间的头文件
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/fs.h>

// cdev.h 中已经包含了
// 用于自动把设备挂载到 /dev/ 下面
#include <linux/device.h>

// 分配内核内存空间的头文件
#include <linux/cdev.h>

// 包含ioremap() 函数
#include <linux/io.h>

// 包含 copy_from_user()
#include <linux/uaccess.h>

#include "gpio_ioctl.h"


#include "rk3566_register.h"

#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 1

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t hello_dev_t;
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

/**
 * 实际的字符设备结构体，类似于面向对象的继承
 */
struct My_character_dev
{
    struct cdev cdev;
    // 自定义的属性和变量
    char c;
    // 指向 此 gpio 数据寄存器 data_register
    void __iomem *gpio_data_register_low;
};

// 定义结构体指针
static struct My_character_dev *my_char_dev;

// 额外创建一个指针
static struct class *my_gpio_class;

// 定义字符设备的操作函数

static int my_char_open(struct inode *inode, struct file *fp)
{
    struct My_character_dev *my_dev;
    printk(KERN_INFO "open my_character_dev %d %d \n", iminor(inode), MINOR(inode->i_cdev->dev));

    my_dev = container_of(inode->i_cdev, struct My_character_dev, cdev);

    fp->private_data = my_dev;
    fp->f_pos = 0;
    return 0;
}

static ssize_t my_char_read(struct file *fp, char __user *buf, size_t count, loff_t *f_ops)
{

    unsigned int gpio3_b4_status;
    struct My_character_dev *my_dev;
    char * output;
    long fail_copy_count;

    printk(KERN_INFO "read my_character_dev\n");

    if (*f_ops > 0)
    {
        return 0;
    }

    my_dev = fp->private_data;

    __ioread32_copy(&gpio3_b4_status, my_dev->gpio_data_register_low, 1);

    // 因为这里只输出一个字节

    output = ( (gpio3_b4_status >> 12 & 0x0001 ) == 1 )?"1\n":"0\n";
    count = 2;
    //unsafe_put_user(output,buf,err);
    // 函数的返回值是未复制的字节数，如果成功，则返回值应该是0。
    fail_copy_count = copy_to_user(buf,output,count);

    if(fail_copy_count){
        // 未完全拷贝
    }

    count -= fail_copy_count;
    *f_ops += count;

    return count;
}

static ssize_t my_char_write(struct file *fp, const char __user *buf, size_t count, loff_t *f_ops)
{
    struct My_character_dev *my_dev;
    unsigned int gpio3_b4_status;

    char * input;
    size_t i;
    char gpio_bit;
    int uncopy_count;

    input = kmalloc(count,GFP_KERNEL);

    if(!input){
        pr_err("allocate kernel buffer fail\n");
        goto err;
    }
    uncopy_count=copy_from_user(input,buf,count);
    if(uncopy_count){
        pr_err("copy buf from user 0 byte, fail, uncopy count: %d\n",uncopy_count);
        goto free;
    }
    count -= uncopy_count;
    //unsafe_get_user(input,buf,err); 可以用，但是不灵活

    printk(KERN_INFO "write my_gpio_dev %lu bytes \n", count);

    for(i=0;i<count;i++){
        pr_info("index %ld : content: %c ,value is %d\n",i,*(input+i),*(input+i));
    }

    my_dev = fp->private_data;

    if( *input=='1'){

        gpio_bit = 1;

    }else{
        
        gpio_bit =0;

    }
    /*
    __user。这是一个Sparse使用的cookie（语义检查器，内核用来查找可能的编码错误）​，
    让开发人员知道他实际上将要使用不可信指针（也就是在当前虚拟地址映射中可能无效的指针）​，
    他不应该间接访问，而应使用专用的内核函数来访问该指针指向的内存。
     */
    gpio3_b4_status = 0x10000000 | gpio_bit <<12 ;

    pr_info("write final content 0x%x\n",gpio3_b4_status);

    __iowrite32_copy(my_dev->gpio_data_register_low, &gpio3_b4_status, 1);
    
    kfree(input);

    return count;
free:
    kfree(input);
err:
    return count; // 不能返回0，否则会不停地往里面写
}

static int my_char_release(struct inode *inode, struct file *fp)
{
    printk(KERN_INFO "release my_character_dev\n");
    return 0;
}

static long my_unlocked_ioctl (struct file * fp, unsigned int command, unsigned long args){

    struct My_character_dev *my_dev;
    unsigned int gpio3_b4_status;
    char status ;

    my_dev = fp->private_data;

    switch (command)
    {
        case OUTPUT_HEIGHT_COMMAND:{
            
            gpio3_b4_status = 0x10001000;
            pr_info("received command OUTPUT_HEIGHT_COMMAND , set gpio3_b4_status = 0x%x\n",gpio3_b4_status );
            __iowrite32_copy(my_dev->gpio_data_register_low, &gpio3_b4_status, 1);

            break;
        }
        case OUTPUT_LOW_COMMAND:{
            gpio3_b4_status = 0x10000000;
            pr_info("received command OUTPUT_LOW_COMMAND , set gpio3_b4_status = 0x%x\n",gpio3_b4_status);

            __iowrite32_copy(my_dev->gpio_data_register_low, &gpio3_b4_status, 1);

            break;
    
        }
        case READ_STATUS_COMMAND:{


            __ioread32_copy(&gpio3_b4_status, my_dev->gpio_data_register_low, 1);

            status= ((gpio3_b4_status>>12)&0x01) == 1 ? '1':'0';

            pr_info("received command READ_STATUS_COMMAND , read gpio3_b4_status = 0x%x,final status is %c\n",gpio3_b4_status,status);

            if(copy_to_user((char *)args,&status,1)){
                return -ENOTTY;
            }

            break;

        }
        
        
    default:
        return -ENOTTY;
      
    }

    return 0;
}

// 把函数注册到结构体中
static struct file_operations my_char_file_ops = {
    .owner = THIS_MODULE,
    .read = my_char_read,
    .write = my_char_write,
    .open = my_char_open,
    .release = my_char_release,
    .unlocked_ioctl=my_unlocked_ioctl
    };

static dev_t make_dev(size_t i)
{
    return MKDEV(hello_major, hello_minor + i);
}

static int __init my_gpio_dev_init(void)
{
    int result;
    size_t i;
    unsigned int gpio3_ddr_low_absolute_addr;
    void __iomem *GRF_gpio3B_iomux;
    void __iomem *gpio3_ddr_low;
    void __iomem *gpio3_dr_low;
    unsigned int io_mux_val;
    unsigned int io_direction_val;

    printk(KERN_INFO "begin to register my_gpio_dev\n");
    if (hello_major)
    {
        hello_dev_t = MKDEV(hello_major, hello_minor);
        // 使用指定的主设备号进行分配
        result = register_chrdev_region(hello_dev_t, hello_nr_devs, "my_gpio_dev");
    }
    else
    {
        result = alloc_chrdev_region(&hello_dev_t, hello_minor, hello_nr_devs, "my_gpio_dev");
        hello_major = MAJOR(hello_dev_t);
    }

    if (result < 0)
    {
        printk(KERN_WARNING "fail to allocate my_gpio_device\n");
        return result;
    }

    // 给字符设备分配空间
    // GFP_KERNEL  代表在内核中分配空间
    my_char_dev = kzalloc(sizeof(struct My_character_dev) * hello_nr_devs, GFP_KERNEL);

    if (!my_char_dev)
    {
        // 避免内存泄漏，必须执行

        printk(KERN_WARNING "fail to allocate kernel memory to my_character_dev !\n");
        goto fail;
    }

    for (i = 0; i < hello_nr_devs; i++)
    {
        // 使用指定的文件操作函数结构体来初始化字符设备
        cdev_init(&my_char_dev[i].cdev, &my_char_file_ops);
        my_char_dev[i].cdev.owner = THIS_MODULE;

        // 将该字符设备添加到系统中
        result = cdev_add(&my_char_dev[i].cdev, make_dev(i), 1);

        if (result)
        {
            printk(KERN_WARNING "fail to initialize my_gpio_dev %d\n", result);
            goto failure_kzalloc;
        }
    }

    // 自动挂载到 /dev/下面
    // 第二个参数是 挂载在 /sys/class 下的名字
    my_gpio_class = class_create(THIS_MODULE, "my_gpio_class");

    if (!my_gpio_class)
    {
        printk(KERN_WARNING "fail to create class! \n");
        goto failure_kzalloc;
    }

    for (i = 0; i < hello_nr_devs; i++)
    {
        dev_t devt = make_dev(i);
        // 真正创建 /dev/my_chara0 设备
        struct device *dev = device_create(my_gpio_class, NULL, devt, NULL, "my_gpio%lu", i);

        /* 配置io映射
         devm_ 前缀的函数
         映射会被关联到提供的 device 结构上。当设备被销毁或卸载时，
         内核会自动调用 iounmap() 来释放所有通过 devm_ioremap() 创建的映射。
         这使得资源管理更加简单和安全，因为您不需要手动跟踪和释放每个映射。
        */
        GRF_gpio3B_iomux = devm_ioremap(dev, GRF_GPIO3B_IOMUX_H, 4);

        // 配置io_mux(io多路复用)为 gpio3_b4
        
        io_mux_val = 0x00030000;
        __iowrite32_copy(GRF_gpio3B_iomux, &io_mux_val, 1);

        gpio3_ddr_low_absolute_addr = GPIO3_BASE_ADDRESS + GPIO_DATA_DIRECTION_REGISTER_LOW_OFFSET;

        gpio3_ddr_low = devm_ioremap(dev, gpio3_ddr_low_absolute_addr, 4);

        // 配置gpio3_b4 引脚为输出模式
        io_direction_val = 0x00010001;
        __iowrite32_copy(gpio3_ddr_low,&io_direction_val , 1);

        // 数据寄存器
        gpio3_dr_low = devm_ioremap(dev, GPIO3_BASE_ADDRESS + GPIO_DATA_REGISTER_LOW_OFFSET, 4);

        my_char_dev[i].gpio_data_register_low = gpio3_dr_low;
    }

    printk(KERN_INFO "register my_gpio_dev successfully!\n");

    return 0;

    // failure_class:

failure_kzalloc:
    kfree(my_char_dev);
fail:
    unregister_chrdev_region(hello_dev_t, hello_nr_devs);
    return 0;
}

static void __exit my_gpio_dev_exit(void)
{

    // 移除设备
    size_t i;
    for (i = 0; i < hello_nr_devs; i++)
    {
        dev_t dev = make_dev(i);

        device_destroy(my_gpio_class, dev);
    }

    class_destroy(my_gpio_class);

    for (i = 0; i < hello_nr_devs; i++)
    {
        cdev_del(&my_char_dev[i].cdev);
    }

    kfree(my_char_dev);

    // 释放申请得到的字符设备号
    unregister_chrdev_region(hello_dev_t, hello_nr_devs);
    printk(KERN_INFO "unregister my_gpio_dev successfully!\n");
}

module_init(my_gpio_dev_init);
module_exit(my_gpio_dev_exit);

//  许可证描述
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("luojunhua");
MODULE_VERSION("V1.0");